package com.hd123.rumba.gwt.widget2.client.dialog;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.hd123.rumba.gwt.widget2.client.misc.RHyperlink;

/**
 * 消息框
 * 
 * @author LiQi
 * 
 */
public class RMsgBox extends RDialog {

  /** 样式 */
  public static final String STYLENAME = "rb-MsgBox";
  public static final String STYLENAME_MESSAGE = "rb-MsgBox-message";
  public static final String STYLENAME_DETAILS = "rb-MsgBox-details";

  /** 图标定义 */
  public static final String ICON_INFO = "images/dialog/icon-info.gif";
  public static final String ICON_WARN = "images/dialog/icon-warning.gif";
  public static final String ICON_ERROR = "images/dialog/icon-error.gif";
  public static final String ICON_QUESTION = "images/dialog/icon-question.gif";

  /** 预定义标题 */
  public static final String TITLE_INFO = "信息";
  public static final String TITLE_WARN = "警告";
  public static final String TITLE_ERROR = "错误";
  public static final String TITLE_QUESTION = "提示";

  public static final String TITLE_CONFIRM = "确认";

  /** servlet返回的异常消息中的JSON键值 */
  public static final String RPC_EXCEPTION_JSONKEY_MESSAGES = "messages";
  public static final String RPC_EXCEPTION_JSONKEY_STACKTRACE = "stackTrace";

  /** 图标和标题的映射 */
  private static Map titleMap;

  static {
    titleMap = new HashMap();
    titleMap.put(ICON_INFO, TITLE_INFO);
    titleMap.put(ICON_WARN, TITLE_WARN);
    titleMap.put(ICON_ERROR, TITLE_ERROR);
    titleMap.put(ICON_QUESTION, TITLE_QUESTION);
  }

  /** 图标 */
  private Image icon;

  /** 消息内容 */
  private HTML message;

  /** 详细信息，例如错误的跟踪堆栈 */
  private RHyperlink toggleDetails;
  private ScrollPanel details;
  private HTML detailsContent;

  /** 消息框最大宽度，超过此宽度消息内容折行 */
  private static final int MAX_WIDTH = 700;

  public RMsgBox() {
    super(false, true);
    setWorkingAreaPadding(10);

    // 根面板
    Grid root = new Grid(1, 2);
    root.setStyleName(STYLENAME);
    root.setCellPadding(10);

    // 图标
    icon = new Image();
    root.setWidget(0, 0, icon);
    root.getCellFormatter().setVerticalAlignment(0, 0, HasAlignment.ALIGN_TOP);

    // 消息和详细信息的容器
    VerticalPanel content = new VerticalPanel();
    content.setSpacing(0);
    root.setWidget(0, 1, content);

    // 消息
    message = new HTML();
    message.setWidth("100%");
    message.setStyleName(STYLENAME_MESSAGE);
    content.add(message);

    // 详细信息
    toggleDetails = new RHyperlink("显示详细信息");
    toggleDetails.addClickHandler(new ToggleDetailsHandler());
    toggleDetails.setVisible(false);
    DOM.setStyleAttribute(toggleDetails.getElement(), "padding", "6px 0px 2px 0px");
    content.add(toggleDetails);

    details = new ScrollPanel();
    details.setAlwaysShowScrollBars(true);
    details.setStyleName(STYLENAME_DETAILS);
    details.setWidth("550px");
    details.setHeight("300px");

    detailsContent = new HTML();
    details.setWidget(detailsContent);
    details.setVisible(false);
    content.add(details);

    setWidget(root);
  }

  /**
   * 显示消息框
   * 
   * @param message
   *          消息内容
   */
  public static void show(String message) {
    show(null, message, null, null, null, null);
  }

  /**
   * 显示消息框
   * 
   * @param message
   *          消息内容
   * @param callback
   *          回调。允许null。
   */
  public static void show(String message, Callback callback) {
    show(null, message, null, null, null, callback);
  }

  /**
   * 显示消息框
   * 
   * @param message
   *          消息内容
   * @param icon
   *          显示的图标，可选值参见常量“ICON_*”。允许null，等价于{@link #ICON_INFO}。
   */
  public static void show(String message, String icon) {
    show(null, message, icon, null, null, null);
  }

  /**
   * 显示消息框
   * 
   * @param message
   *          消息内容
   * @param icon
   *          显示的图标，可选值参见常量“ICON_*”。允许null，等价于{@link #ICON_INFO}。
   * @param buttons
   *          对话框按钮。允许null，表示只有一个{@link #BUTTON_OK}按钮。
   * @param callback
   *          回调。允许null。
   */
  public static void show(String message, String icon, ButtonConfig[] buttons,
      ButtonConfig focusedButton, Callback callback) {
    show(null, message, icon, buttons, focusedButton, callback);
  }

  /**
   * 显示消息框
   * 
   * @param title
   *          消息对话框的标题。允许null，表示使用默认的TITLE_* 。
   * @param message
   *          消息内容
   * @param icon
   *          显示的图标，可选值参见常量“ICON_*”。允许null，等价于{@link #ICON_INFO}。
   * @param buttons
   *          对话框按钮。允许null，表示只有一个{@link #BUTTON_OK}
   *          按钮，此时这个按钮会自动获得焦点。
   * @param focusedButton
   *          对话框显示时获得焦点的按钮。允许null，表示没有任何按钮获得焦点
   * @param callback
   *          回调。允许null。
   */
  public static void show(String title, String message, String icon, ButtonConfig[] buttons,
      ButtonConfig focusedButton, final Callback callback) {
    show(title, message, null, icon, buttons, focusedButton, callback);
  }

  /**
   * 显示消息框
   * 
   * @param title
   *          消息对话框的标题。允许null，表示使用默认的TITLE_* 。
   * @param message
   *          消息内容
   * @param details
   *          详细信息，允许null
   * @param icon
   *          显示的图标，可选值参见常量“ICON_*”。允许null，等价于{@link #ICON_INFO}。
   * @param buttons
   *          对话框按钮。允许null，表示只有一个{@link #BUTTON_OK}
   *          按钮，此时这个按钮会自动获得焦点。
   * @param focusedButton
   *          对话框显示时获得焦点的按钮。允许null，表示没有任何按钮获得焦点
   * @param callback
   *          回调。允许null。
   */
  private static void show(String title, String message, String details, String icon,
      ButtonConfig[] buttons, ButtonConfig focusedButton, final Callback callback) {
    RMsgBox msgBox = new RMsgBox();

    if (icon == null)
      icon = ICON_INFO;
    msgBox.setIcon(icon);

    if (title == null)
      title = (String) titleMap.get(icon);
    msgBox.setCaptionText(title);

    msgBox.setFocusedButton(focusedButton);

    if (buttons == null) {
      buttons = new ButtonConfig[] {
        BUTTON_OK };
      msgBox.setFocusedButton(BUTTON_OK);
    }
    msgBox.setButtons(buttons);

    msgBox.setMessage(message);
    if (details != null)
      msgBox.setDetails(details);
    msgBox.setShowCloseButton(false);
    msgBox.setEscKeyToHide(true);

    if (callback != null) {
      msgBox.addCloseHandler(new CloseHandler<PopupPanel>() {
        public void onClose(CloseEvent<PopupPanel> event) {
          ButtonConfig clickedButton = ((RDialog) event.getSource()).getClickedButton();
          callback.onClosed(clickedButton);
        }
      });
    }

    msgBox.center();
  }

  /**
   * 错误信息对话框
   * 
   * @param message
   *          消息内容
   */
  public static void showError(String message) {
    showError(message, null, null, null, null);
  }

  /**
   * 错误信息对话框
   * 
   * @param t
   *          错误。
   */
  public static void showError(Throwable t) {
    showError(null, t, null, null, null);
  }

  /**
   * 错误信息对话框
   * 
   * @param message
   *          消息内容
   * @param t
   *          错误。允许null。
   */
  public static void showError(String message, Throwable t) {
    showError(message, t, null, null, null);
  }

  /**
   * 错误信息对话框，确定后回退
   * 
   * @param message
   *          消息内容
   * @param t
   *          错误。允许null。
   */
  public static void showErrorAndBack(String message, Throwable t) {
    showError(message, t, null, null, new Callback() {
      public void onClosed(ButtonConfig clickedButton) {
        History.back();
      }
    });
  }

  /**
   * 错误信息对话框
   * 
   * @param message
   *          消息内容
   * @param t
   *          错误。允许null。
   * @param callback
   *          回调。允许null。
   */
  public static void showError(String message, Throwable t, Callback callback) {
    showError(message, t, null, null, callback);
  }

  /**
   * 错误信息对话框
   * 
   * @param message
   *          消息内容
   * @param t
   *          错误。允许null。
   * @param buttons
   *          对话框按钮。允许null，表示只有一个{@link #BUTTON_OK}
   *          按钮，此时这个按钮会自动获得焦点。
   * @param focusedButton
   *          对话框显示时获得焦点的按钮。允许null，表示没有任何按钮获得焦点
   * @param callback
   *          回调。允许null。
   */
  public static void showError(String message, Throwable t, ButtonConfig[] buttons,
      ButtonConfig focusedButton, Callback callback) {
    JSONObject json = null;
    boolean isJson = true;
    try {
      json = (JSONObject) JSONParser.parse(t.getMessage());
    } catch (Exception e) {
      isJson = false;
    }

    // 错误消息
    StringBuffer sbMessage = new StringBuffer();
    if (message != null)
      sbMessage.append(message);

    String details = null; // 详细信息
    if (isJson) {
      // 解析由RPC.encodeExceptionForGWTClient序列化的嵌套异常消息和跟踪堆栈
      JSONArray messages = (JSONArray) json.get(RPC_EXCEPTION_JSONKEY_MESSAGES);
      for (int i = 0; i < messages.size(); i++) {
        JSONString msg = (JSONString) messages.get(i);

        // 拆分可能被RBRemoteServiceServlet.buildException拼出的字符串
        String msgSlices[] = msg.stringValue().split("=>");
        for (String msgSlice : msgSlices) {
          if (sbMessage.length() > 0)
            sbMessage.append("<br>&nbsp;&nbsp;&nbsp;&nbsp;原因：");
          sbMessage.append(msgSlice);
        }
      }

      if (json.containsKey(RPC_EXCEPTION_JSONKEY_STACKTRACE)) {
        JSONString stackTrace = (JSONString) json.get(RPC_EXCEPTION_JSONKEY_STACKTRACE);
        details = "<pre style='font-family:tahoma; margin:0px;'>" + stackTrace.stringValue()
            + "</pre>";
      }
    } else {
      Throwable tt = t;
      while (tt != null) {
        String msg = tt.getMessage();
        if (msg != null) {
          if (sbMessage.length() > 0)
            sbMessage.append("<br>&nbsp;&nbsp;&nbsp;&nbsp;原因：");
          sbMessage.append(msg);
        }
        tt = tt.getCause();
      }
    }

    show(null, sbMessage.toString(), details, ICON_ERROR, buttons, focusedButton, callback);
  }

  /**
   * 显示确认对话框，焦点放在"是"按钮上
   * 
   * @param message
   *          提示信息内容
   * @param callback
   *          回调
   */
  public static void showConfirm(String message, ConfirmCallback callback) {
    showConfirm(message, true, callback);
  }

  /**
   * 显示确认对话框
   * 
   * @param message
   *          提示信息内容
   * @param focusOnOKButton
   *          是否把焦点放在"确定"按钮上，这样用户可以直接按回车确认
   * @param callback
   *          回调
   */
  public static void showConfirm(String message, boolean focusOnOKButton,
      final ConfirmCallback callback) {
    Callback callbackAdapter = new Callback() {
      public void onClosed(ButtonConfig clickedButton) {
        callback.onClosed(clickedButton != null && clickedButton.equals(BUTTON_OK));
      }
    };
    show(TITLE_CONFIRM, message, ICON_QUESTION, BUTTONS_OKCANCEL, focusOnOKButton ? BUTTON_OK
        : null, callbackAdapter);
  }

  /**
   * 设置图标
   * 
   * @param iconUrl
   */
  public void setIcon(String iconUrl) {
    icon.setUrl(iconUrl);
  }

  /**
   * 设置消息内容
   * 
   * @param message
   */
  public void setMessage(String message) {
    this.message.setHTML(message);
  }

  /**
   * 设置详细信息
   * 
   * @param details
   */
  public void setDetails(String details) {
    toggleDetails.setVisible(details != null && !details.equals(""));
    detailsContent.setHTML(details);
  }

  public void show() {
    super.show();

    // 设置消息框的最大宽度
    if (getOffsetWidth() > MAX_WIDTH)
      setWidth(new Integer(MAX_WIDTH).toString() + "px");
  }

  /**
   * 显示/隐藏详细信息链接的事件处理
   * 
   * @author LiQi
   * 
   */
  private class ToggleDetailsHandler implements ClickHandler {
    public void onClick(ClickEvent event) {
      details.setVisible(!details.isVisible());
      toggleDetails.setHTML(details.isVisible() ? "隐藏详细信息" : "显示详细信息");
      center();
    }
  }

  /**
   * 消息框回调接口
   * 
   * @author LiQi
   * 
   */
  public static interface Callback {

    /**
     * 当消息框关闭时触发
     * 
     * @param clickedButton
     *          被点击的按钮，为null表明用户是按ESC键关闭消息框的
     */
    void onClosed(ButtonConfig clickedButton);
  }

  /**
   * 确认对话框回调接口
   * 
   * @author LiQi
   * 
   */
  public static interface ConfirmCallback {

    /**
     * 当消息框关闭时触发
     * 
     * @param confirmed
     *          是否确认，即用户是否点击了“是”按钮
     */
    void onClosed(boolean confirmed);
  }
}
